import datetime
import json
import time
# import multitasking

from playwright.sync_api import sync_playwright
import math
import qrcode
import threading
from xhs import DataFetchError, XhsClient, help
import openpyxl
from openpyxl import Workbook
from openpyxl.drawing.image import Image
import os
from common_utils import *
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED, ALL_COMPLETED
import _thread

commentsFileName= "%s_commnets_%s.json"
keywordFile = "%s_keywordresult_%s.json"
excel_file_path = '%s_valid_comments_%s.xlsx'

need_comment = True
threadNum = 6

time_distance = 90

lock = threading.Lock()
commentNum = 0

proces_config = {"is_in111111": False,
                  "is_in222222":False,
                  "is_in333333" : False,
                  "is_in444444" : False,
                  "is_in555555" : False
                }

def sign(uri, data=None, a1="", web_session=""):
    for _ in range(10):
        try:
            with sync_playwright() as playwright:
                stealth_js_path = "stealth.min.js"
                chromium = playwright.chromium
                browser = chromium.launch(headless=True)

                browser_context = browser.new_context()
                browser_context.add_init_script(path=stealth_js_path)
                context_page = browser_context.new_page()
                context_page.goto("https://www.xiaohongshu.com")
                browser_context.add_cookies([
                    {'name': 'a1', 'value': a1, 'domain': ".xiaohongshu.com", 'path': "/"}]
                )
                context_page.reload()
                time.sleep(1)
                encrypt_params = context_page.evaluate("([url, data]) => window._webmsxyw(url, data)", [uri, data])
                return {
                    "x-s": encrypt_params["X-s"],
                    "x-t": str(encrypt_params["X-t"])
                }
        except Exception as e:
            logger.error(f"the sign error :{str(e)}")
            pass
    raise Exception(" too more fail, exit")


def __record_err__(id, msg, isError = True):

        with open("xhs_config.json", "r") as file:
            config =json.load(file)
            file.close()

        timeNow=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        config[id]['msg'] = f"{timeNow}:{msg}"
        if isError:
           config[id]['errorTime'] = time.time()
        else:
           config[id]['errorTime'] = 0

        with open("xhs_config.json", "w") as file:
            json.dump(config, file, indent=4, ensure_ascii=False)
            file.close()

def  retry_excute(fn,key, *args):
    for _ in range(3):
        try: 
            return fn(args)
        except DataFetchError as e:
            logger.error(f"excute {fn} {key} error:{str(e)}")
            time.sleep(3)

            msg = "访问小红书异常"
            if "网页版搜索次数已达今日上限" in str(e):
                msg = "cookie网页版搜索次数已达今日上限"
            elif "登录已过期" in str(e):
                msg = "cookie登录已过期"
            
            __record_err__(key, msg)
            return None

def __split__(mapIds:dict, n = threadNum):
    if not mapIds: 
        return []
    
    arr = list(mapIds.keys())
    if len(arr) <= 6:
        return [arr]

    n = int(math.ceil(len(arr) / float(n)))
    return [arr[i:i + n] for i in range(0, len(arr), n)]


def collect_onecomment(xhs_client, key, noteId, validTime, tag):
    start = time.time()
    if noteId  == "keywords":
        return 
    
    comments = None
    try:
        comments = xhs_client.get_note_all_comments(noteId)
    except Exception as e :
        logger.warning(f"fail to get commnet {noteId}, {str(e)}")

        msg = "访问小红书异常"
        needStop = False
        if "网页版搜索次数已达今日上限" in str(e):
            msg = "网页版搜索次数已达今日上限"
            needStop = True
        elif "登录已过期" in str(e):
            msg = "登录已过期"
            needStop = True
        
        needStop and __record_err__(key, msg)
        if needStop:
            logger.warning(f"需要停止继续检索")
            return


    if not comments:
        comments = []

    logger.warning(f"finish get note {noteId} , 评论数: {len(comments)}")
    commentsReturn = {}

    if comments and len(comments) > 0 :
        logger.info(f"now  parse comments: {noteId}")
        for c in comments: 
            createTime = c['create_time'] 
            now = time.time() * 1000

            ##print(f"now {now}, create {createTime} , discount = {now - createTime}, validTime {validTime}")
            logger.debug(f"now {now}, create {createTime} , discount = {now - createTime}, validTime {validTime}")
            if  now - createTime > validTime:
                continue

            logger.warning(f"find valid comment")
            #print(f"find valid comment")

            tmpMap = {}

            try:
                tmpMap["create_time"] = c['create_time']
                tmpMap['local_time'] =  time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(c['create_time']/1000))
                uid = c['user_info']['user_id']
                tmpMap['userurl'] =  f"https://www.xiaohongshu.com/user/profile/{uid}"
                tmpMap['nickname'] =  c['user_info']['nickname']
                tmpMap['content'] =  c['content']
                tmpMap['note'] =  f"https://www.xiaohongshu.com/explore/{noteId}"

            except Exception as e :
                logger.error(f"parse comments error: {c} , {str(e)}")
                #print(f"error : {c}")
                #print(str(e))
            
            qr = qrcode.QRCode(
                version=2,
                error_correction=qrcode.constants.ERROR_CORRECT_L,
                box_size=10,
                border=1
            )

            #print(f"the valid comment: {tmpMap}")
            qr.add_data(tmpMap['userurl'])
            qr.make(fit=True)
            img = qr.make_image()
            img.save(f"static/{c['id']}_{uid}_qrcode.png")

            tmpMap['img'] = f"static/{c['id']}_{uid}_qrcode.png"

            lock.acquire()
            global commentNum
            commentNum+=1
            tmpMap['no'] = commentNum
            lock.release()
            

            commentsReturn[c['id']] = tmpMap

            logger.warning(f"the valid comment: {tmpMap}")

    # 读取JSON文件
    with open(keywordFile % (key, tag), "r",) as file:
        data = json.load(file)
    file.close()

    # 修改JSON数据
    data[noteId]['finish'] = True

    # 将修改后的数据写回文件中
    with open(keywordFile % (key, tag), "w") as file:
        json.dump(data, file, indent=4, ensure_ascii=False)
    
    file.close()

    logger.warning(f"the valid commets: {commentsReturn}")
    comments = {}
    if os.path.exists(commentsFileName % (key, tag)):
      with open((commentsFileName % (key, tag)), "r") as file:
        try:
            comments = json.load(file)
            if not comments:
                comments = {}
        except Exception as e:
            comments = {}
    
      file.close()

    comments.update(commentsReturn)
    #logger.warning(f"the add comments: {comments}")
    with open((commentsFileName % (key, tag)), 'w+') as writeF:
        json.dump(comments, writeF, indent=4, ensure_ascii=False)

    writeF.close()
    end  = time.time()
    logger.warning(f"the note {noteId} cost time : {(end - start) / 60} 分钟")
    #print(f"the note {noteId}  cost time : {(end - start) / 60} 分钟")

    return "success"

def  collect_validcomments(xhs_client, key, collectNotesMap, validTime, tag='current'):

    #print(f"now collect valid comments:{key}")
    start = time.time()

    #清除老的comments 文件
    try:
       os.remove(commentsFileName % (key, tag))
    except Exception as e :
        #print(f"delete fail {str(e)}")
        logger.error(f"delete fail {str(e)}")

    lock.acquire()
    global commentNum
    commentNum = 0
    lock.release()

    for id in collectNotesMap.keys():
        if id  == "keywords":
            continue

        with ThreadPoolExecutor(max_workers=3) as t: 
            task  = t.submit(collect_onecomment, xhs_client, key , id , validTime, tag)
            aa =task
            t.shutdown()

    end = time.time()
    logger.warning(f"{key} total valid comments cost time : {(end - start) / 60} 分钟")

    global proces_config

    proces_config[f'is_in{key}'] = False

    __record_err__(key, "收集评论完成....", isError=False)


def collect_notes(xhs_client, key, keywords, validTime, limit_count):

    #print(f"start to collect : {datetime.datetime.now()} , keywords:{keywords} - validTime ms {validTime} , limit_count:{limit_count}")
    logger.warning(f"start {key} to collect : {datetime.datetime.now()} , keywords:{keywords} - validTime ms {validTime} , limit_count:{limit_count}")
    
    start = time.time()
    collectNotesMap = {}
    n = 1
    pageSize = 20 
    __record_err__(key, "正在根据关键词检索帖子....", isError=False)

    while True: 
        noteMap = retry_excute(xhs_client.get_note_by_keyword, key, keywords, n , pageSize)
        if noteMap:
            logger.warning(f"{key} the iterms len {len(noteMap['items'])}")
            for note in noteMap["items"]:
                tmpMap = {}
                try:
                    if note['model_type'] == "note":
                        
                        if 'display_title' in note['note_card'].keys():
                            tmpMap["titile"] = note['note_card']['display_title']
                        else:
                            tmpMap["titile"] = ""

                        tmpMap["nickName"] = note['note_card']['user']['nick_name']
                        tmpMap["likeCount"] = note['note_card']['interact_info']['liked_count']
                        tmpMap["finish"] = False
                        n = n + 1 

                        collectNotesMap[note['id']] = tmpMap

                except Exception as e :
                    logger.error(f"{key} parse note error: {note} , {str(e)}")
                    # #print(f"error : {note}")
                    # #print(str(e))

                

            if not noteMap['has_more'] or n >= limit_count:
                logger.warning(f"finish {key} parse notes : {noteMap['has_more']} ,  n:{n} , limitcount :{limit_count}")
                #print(f"finish parse notes : {noteMap['has_more']} ,  n:{n}  notes count limit to: {limit_count}")
                break;
        else:
            logger.error(f"the query from xhs error, key={key}, exit")
            return 
            
    #print(f"the collect total items, count: {len(collectNotesMap.keys())}")
    logger.warning(f" {key} the collect total items, count: {len(collectNotesMap.keys())}")
    
    tag =  'current'
    #collectNotesMap['keywords'] = keywords

    with open((keywordFile % (key, tag)), "w+") as file:
      file.write(json.dumps(collectNotesMap, indent=4, ensure_ascii=False))
      file.close()

    __record_err__(key, "收集帖子完成，开始收集评论....", isError=False)

    end  = time.time()
    #print(f"the collect: {len(collectNotesMap.keys())}  notes, cost time : {(end - start) / 60} 分钟")
    logger.warning(f"the {key} collect: {len(collectNotesMap.keys())}  notes, cost time : {(end - start) / 60} 分钟")

    return collectNotesMap

def gen_excel(tag):
    #print(f"now to generate excel for tag {tag}") 
    logger.warning(f"now to generate excel for tag {tag}") 

    start = time.time()
    excel_file_path = f'valid_comments_{str(tag)}.xls'
    wb = Workbook() 
    sheet = wb.active
    comments = None
    with open((commentsFileName % tag), "r") as file:
      comments = json.load(file)
      file.close()

    #print(comments)
    image_column = "A"

    if comments:
        for i in range(len(comments)):
            try:
                img = Image(comments[i]['img'])
                img.width, img.height = (120, 120)
                sheet.column_dimensions[image_column].width = 15  
                sheet.row_dimensions[i].height = 90
                sheet.add_image(img, anchor=image_column + str(i + 1))

                sheet[f"B{i +1 }"] = comments[i]['content']
                sheet[f"C{i +1}"] = comments[i]['nickname']
                sheet[f"D{i +1}"] = comments[i]['local_time']
            except Exception as e:  
                #print(e)
                logger.error(f"excel sheet save {str(e)}")
        
    wb.save(excel_file_path)  # 保存

    end = time.time()

    #print(f'save. excel finish, cost time : {(end - start) / 60} 分钟')


def start_collect(key):
    with open("xhs_config.json", "r") as file:
        config = json.load(file)
        file.close()

    if not config:
        logger.error("xhs_config.json 不存在")
        exit(1)

    cookie = config[key]['cookie']
    keywords = config[key]['keywords']
    validTimeConf = config[key]['validTime']
    limit_count = config[key]['limit_count']
    if not cookie or not keywords or not validTimeConf or not limit_count:
        logger.error(f"配置文件配置不全， {key} : cookie keywords validTime limit_count 都需要")
        exit(1)
   
    errorTime = config[key]['errorTime'] if  "errorTime" in config[key].keys() else 0
    updateTime = config[key]['updateTime'] if 'updateTime' in config[key].keys() else 0 
    timeNow = time.time()

    if timeNow - errorTime < 5 * 60 and  errorTime > updateTime and  errorTime - updateTime > time_distance:
        logger.error(f"the key :{key} cookie errror, need reconfig,exit")
        return

    validTime = validTimeConf * 60 * 1000
    xhs_client = XhsClient(cookie, sign=sign)
    lock.locked()
    global proces_config
    if not proces_config[f'is_in{key}']:
        proces_config[f'is_in{key}'] = True
        #print(f"start to collect {key}")
        notesMap = collect_notes(xhs_client, key, keywords, validTime, limit_count)
        collect_validcomments(xhs_client, key, notesMap, validTime)
    
    time.sleep(50)
    logger.info(f"finish to collect {key}")

def task_begin(key):
    logger.warning(f"now start task {key}")

    with open("xhs_config.json", "r") as file:
        config = json.load(file)
        file.close()

    if not config:
        logger.error("配置文件不存在")
        #print("配置文件不存在，请先配置")
        exit(1)
    
    start_collect(key)
